/*
 * ============================================================================
 *                   GNU Lesser General Public License
 * ============================================================================
 *
 *
 *
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307, USA.
 * 
 *
 *
 */
package rad.framework.security.encryption;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.PostLoad;
import javax.persistence.PostPersist;
import javax.persistence.PostUpdate;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;

/**
 * Attach this callback listener to any entity bean that has properties
 * annotation with the Encrypted annotation.
 * 
 * @author boudyacho
 * 
 */
public class EncryptionCallbackListener {

	/**
	 * This will find all properties annotation as Encrypted and encrypt them
	 * before saving to the database.
	 * 
	 * @param o
	 */
	@PrePersist
	@PreUpdate
	public void encrypt(Object o) {
		List<Method> methods = getEncrypted(o);
		for (Method method : methods) {
			String value = get(o, method);
			if (value == null) continue;
			Encryptor encryptor = getEncryptor(method);
			value = encryptor.encrypt(value);
			set(o, method, value);
		}
	}

	/**
	 * This will find all properties annotation as Encrypted and decrypt them so
	 * that they are usable in memory.
	 * 
	 * @param o
	 */
	@PostPersist
	@PostUpdate
	@PostLoad
	public void decrypt(Object o) {
		List<Method> methods = getEncrypted(o);
		for (Method method : methods) {
			String value = get(o, method);
			if (value == null) continue;
			Encryptor encryptor = getEncryptor(method);
			value = encryptor.decrypt(value);
			set(o, method, value);
		}
	}

	/**
	 * Finds annotated fields.
	 * 
	 * @param o
	 * @return
	 */
	private List<Method> getEncrypted(Object o) {
		Method[] getters = o.getClass().getMethods();
		List<Method> encrypted = new ArrayList<Method>();
		for (Method method : getters) {
			if (method.getAnnotation(Encrypted.class) != null) {
				encrypted.add(method);
			}
		}
		return encrypted;
	}

	/**
	 * Creates an instance of the encryptor specified by the annotation.
	 * 
	 * @param m
	 * @return
	 */
	protected Encryptor getEncryptor(Method m) {
		Encrypted e = m.getAnnotation(Encrypted.class);
		try {
			return (Encryptor) e.encryptor().newInstance();
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
	}

	/**
	 * Gets the value via the property accessor.
	 * 
	 * @param o
	 * @param m
	 * @return
	 */
	private String get(Object o, Method m) {
		try {
			return (String) m.invoke(o, new Object[0]);
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
	}

	/**
	 * Sets the value via the property mutator.
	 * 
	 * @param o
	 * @param m
	 * @param value
	 */
	private void set(Object o, Method m, String value) {
		try {
			String name = m.getName().substring(3);
			m = o.getClass().getMethod("set" + name,
					new Class[] { String.class });
			m.invoke(o, new Object[] { value });
		} catch (Exception ex) {
			throw new RuntimeException(ex);
		}
	}
}
